package mweb

import (
	"fmt"
	"gitee.com/dennis-mxx/mxx-core/mexception"
	"gitee.com/dennis-mxx/mxx-core/mlogger"
	"gitee.com/dennis-mxx/mxx-core/msystem"
	"gitee.com/dennis-mxx/mxx-core/mutil"
	"github.com/go-playground/validator"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	"github.com/kataras/iris/v12/sessions"
	"github.com/kataras/iris/v12/sessions/sessiondb/redis"
	"mime/multipart"
	"net/http"
	"strconv"
	"time"
)

var EmptyExceptionHandler = func(ctx *RequestContext, err error) {
	mlogger.FormatError(err)
	ctx.Ctx.StatusCode(500)

}

func InitIris() {
	if msystem.Iris.FileMaxSize == 0 {
		msystem.Iris.FileMaxSize = 1024 * 1024 * 2
	}
	if msystem.Iris.ApplicationName == "" {
		msystem.Iris.ApplicationName = "DEFAULT-IRIS"
	}
	if msystem.Iris.Port == 0 {
		msystem.Iris.Port = 8080
	}
}

type irisWebApplication struct {
	ApplicationName    string
	Port               int
	Charset            string
	TimeFormat         string
	ContextPath        string
	exceptionHandler   func(ctx *RequestContext, err error)
	Iris               *iris.Application
	IrisSession        *sessions.Sessions
	Validate           *validator.Validate
	InterceptorStorage InterceptorStorage
}

func NewSimpleWebApplication(irisOptions *msystem.IrisOptions) *irisWebApplication {
	return &irisWebApplication{
		ApplicationName:    irisOptions.ApplicationName,
		Port:               irisOptions.Port,
		Charset:            "UTF-8",
		TimeFormat:         mutil.DateUtil.DateTimeFormat,
		ContextPath:        irisOptions.ContextPath,
		Validate:           validator.New(),
		InterceptorStorage: &map[Tag]Interceptor{},
	}
}

func NewWebApplication(applicationName string, port int, charset string, timeFormat string, contextPath string) *irisWebApplication {
	return &irisWebApplication{
		ApplicationName:    applicationName,
		Port:               port,
		Charset:            charset,
		TimeFormat:         timeFormat,
		ContextPath:        contextPath,
		Validate:           validator.New(),
		InterceptorStorage: &map[Tag]Interceptor{},
	}
}

func (domain *irisWebApplication) Use(adapter HandleAdapter) *irisWebApplication {
	domain.Iris.Use(func(c *context.Context) {
		ctx := c.Values().Get("RequestContext")

		defer func() {
			defer func() {
				if error := recover(); error != nil {
					mlogger.Logger.Error(error)
				}
			}()
			if err := recover(); err != nil {
				requestContext := ctx.(*RequestContext)
				exceptionHandler := (ctx.(*RequestContext)).Application.exceptionHandler
				exceptionHandler(requestContext, err.(error))
			}
		}()

		if ctx == nil {
			requestContext := &RequestContext{
				Application: domain,
				Ctx:         c,
				Url:         c.Path(),
				ContentType: c.GetHeader("content-type"),
				UserAgent:   c.GetHeader("User-Agent"),
				Method:      c.Method(),
				sess:        domain.IrisSession,
				Validate:    domain.Validate,
			}
			c.Values().Set("RequestContext", requestContext)
		}
		adapter(ctx.(*RequestContext))
	})
	return domain
}
func (domain *irisWebApplication) WithCors() *irisWebApplication {
	cors := func(ctx iris.Context) {
		ctx.Header("Access-Control-Allow-Origin", ctx.GetHeader("Origin"))
		ctx.Header("Access-Control-Allow-Credentials", "true")
		ctx.Header("Access-Control-Allow-Headers", "*")
		ctx.Header("Access-Control-Allow-Methods", "*")

		if ctx.Method() == "OPTIONS" {
			ctx.StatusCode(http.StatusOK)
			return
		}
		ctx.Next()
	}
	domain.Iris.Handle("OPTIONS", "*", cors)
	domain.Iris.Use(cors)
	return domain
}
func (domain *irisWebApplication) WithRoute(routes ...*Route) *irisWebApplication {
	app := domain.Iris
	if app == nil {
		panic(mexception.NewException(mexception.NilException, "iris not initialized"))
	}
	for _, route := range routes {
		var interceptors []Interceptor
		for _, tag := range route.Tag {
			if interceptor := (*domain.InterceptorStorage)[tag]; interceptor != nil {
				interceptors = append(interceptors, interceptor)
			}
		}
		if interceptors == nil {
			interceptors = make([]Interceptor, 0)
		}
		actuator := &Actuator{route: route, ctx: domain, interceptors: interceptors}
		app.Handle(route.Method, route.Path, actuator.execute)
	}
	return domain
}

func (domain *irisWebApplication) ExceptionHandler(exceptionHandler func(ctx *RequestContext, err error)) {
	domain.exceptionHandler = exceptionHandler
}

func (domain *irisWebApplication) AddInterceptor(tag Tag, interceptor Interceptor) {
	(*domain.InterceptorStorage)[tag] = interceptor
}

func (domain *irisWebApplication) Run() {
	RUN.ShowDefault()
	if domain.exceptionHandler == nil {
		domain.exceptionHandler = EmptyExceptionHandler
	}
	err := domain.Iris.Run(iris.Addr(fmt.Sprintf(":%v", domain.Port)), iris.WithoutServerError(iris.ErrServerClosed))
	if err != nil {
		mlogger.FormatError(err)
	}
}

func (domain *irisWebApplication) FileSaveToLocal(multipartFile multipart.File, fileHeader *multipart.FileHeader, err error) (writePath string, errs error) {
	if err != nil {
		return "", err
	}
	if fileHeader.Size > msystem.Iris.FileMaxSize {
		return "", mexception.NewException(mexception.FileToMaxException, "upload file to max")
	}
	if msystem.Iris.FileStoragePath == "" {
		panic(mexception.NewException(mexception.UnsupportedException, "unsupported empty file storage path"))
	}
	return mutil.Upload.SystemLocalFileStorage(multipartFile, fileHeader.Size, msystem.Iris.FileStoragePath, fileHeader.Filename)
}

func (domain *irisWebApplication) NewIris(isRedis bool) *irisWebApplication {
	app := iris.New()
	app.Configure(iris.WithConfiguration(
		iris.Configuration{
			Charset:    domain.Charset,
			TimeFormat: domain.TimeFormat}),
		iris.WithoutServerError(iris.ErrServerClosed),
		iris.WithOptimizations)
	session := sessions.New(sessions.Config{
		Cookie:  "sessionId",
		Expires: 24 * time.Hour,
	})

	if isRedis {
		redisOption := msystem.Redis
		config := redis.Config{
			Network:  redisOption.Network,
			Addr:     redisOption.Addr,
			Database: strconv.Itoa(redisOption.DB),
			Password: redisOption.Password,
			Prefix:   fmt.Sprintf("%s:", domain.ApplicationName),
		}
		database := redis.New(config)
		if database == nil {
			panic(mexception.NewException(mexception.RedisException, fmt.Sprintf("connection to redis session exception %s:%s [%s]", config.Network, config.Addr, config.Database)))
		} else {
			mlogger.Logger.Info(fmt.Sprintf("connection to redis session {%s:%s [%s]} successful", config.Network, config.Addr, config.Database))
		}
		session.UseDatabase(database)
	}
	domain.IrisSession = session
	domain.Iris = app
	return domain
}
